package ufba.mata61.compilador.app.core;

import java.util.EnumSet;

import ufba.mata61.compilador.app.core.com.IReservedWord;
import ufba.mata61.compilador.app.core.com.IState;
import ufba.mata61.compilador.app.core.com.IStatusError;
import ufba.mata61.compilador.app.core.com.IToken;
import ufba.mata61.compilador.internal.util.IOOperation;

/**
 * <p>This class convert a sequence of characters into a sequence 
 * of tokens while performs lexical analysis. The <b>Scanner</b> 
 * determines which tokens are ultimately sent to the parser and, therefore, 
 * is able to throw out things that are not defined in the grammar, 
 * like comments. The scanner cares about things like characters 
 * (A-Z and the usual symbols), numbers (0-9), characters that define 
 * operations (such as +, -, *, and /), quotation marks for string encapsulation, 
 * and semicolons.
 * </p>
 * 
 * @author Joao Pedro Bittencourt
 *
 */
public class Scanner {
	
	private final char EOF = (char)-1;
	
	private IOOperation op;
	
	/**
	 * 
	 * @param sourceCode locate at the disk of the source code that
	 * should be analysis by Scanner
	 */
	public Scanner(String sourceCode) {
		op = new IOOperation(sourceCode);
	}
	
	/**
	 * @return the token found 
	 *         <p>error if any problem are found</p> 
	 */
	public String scan(){
	    String state = IState.START;
	    String currentToken = IStatusError.ERROR;
	    String token = "";
	    char previous = EOF;
	    boolean get, num0 = false;
	    
	    
	    while (state != IState .DONE) {
//	    	System.out.println("state: "+state);
	        char t = op.getNextChar();
	        get = true;
	        if (state.equals(IState.START)){
        		if (isNumber(t)){
        			if(t == 48) num0 = true;
        			state = IState.NUM;
        		}
                else if  (isCharacter(t) || t == '_')
                    state = IState.IDENTIFIER;
                else if (op.isEOF()){
                    state = IState.DONE;
                    currentToken = IToken.EOF;
                    get = false;
                }  
                else if(t == '=')
                    state = IState.EQUAL;
                else if (t == '~')
                    state = IState.DIFFERENT;
                else if (t == '<')
                    state = IState.LESS_THEN;
                else if (t == '>')
                    state = IState.GREATER_THEN;
                else if (t == '.')
                    state = IState.DOT;
                else if(t == '\'' || t == '\"'){
                    state = IState.WORD;
                    previous = t;
                }
                else if(t == '[')
                     state = IState.MULTI_WORD;
                else if(t == '-')
                	state = IState.EN_DASH;
                else {
                    get = false;
                    state = IState.DONE;
                    switch (t){
                        case ' ' : case '\n' : case '\t' :
                            state = IState.START;
                            break;
                        case '+' :
                            currentToken = IToken.PLUS;
                            break;    
                        case '*' :
                            currentToken = IToken.MULTIPLY;
                            break;
                        case '/' :
                            currentToken = IToken.DIVISION;
                            break;
                        case '%':
                            currentToken = IToken.MODULO;
                            break;
                        case '^':
                            currentToken = IToken.POW;
                            break;
                        case '#' : 
                            currentToken = IToken.CARDINALITY;
                            break;
                        case '(' :
                            currentToken = IToken.LEFT_PARENTHESIS;
                            break;
                        case ')' :
                            currentToken = IToken.RIGHT_PARENTHESIS;
                            break;
                        case ']' :
                            currentToken = IToken.RIGHT_BRACKETS;
                            break;
                        case '{' :
                            currentToken = IToken.OPEN_KEY;
                            break;
                        case '}' :
                            currentToken = IToken.CLOSE_KEY;
                            break;
                        case ':' :
                            currentToken = IToken.COLON;
                            break;
                        case ';' :
                            currentToken = IToken.SEMI_COLON;
                            break;
                        case ',' :
                            currentToken = IToken.COMMA;
                            break;
                        default :
                            currentToken = IStatusError.ERROR;
                            state = IState.DONE;
                            break;
                    }
                }
	        }
	        else if (state.equals(IState.NUM)){
                if (!isNumber(t)){
                	if(t == '.'){
                		state = IState.NUM_DEC;
                		previous = '.';
                	}
                	else if ((t == 'x') && num0){ state = IState.NUM_HEXA; num0 = false;}
                	else if ((t == 'e') || (t == 'E')){
                		state = IState.NUM_EXP;
                		previous = 'e';
                	}
                	else if (isAlphaNum(t)) state = IState.NOT_NUM;
                    else{
                        currentToken = IToken.NUMBER;
                        state = IState.DONE;
                        if (! (t == ' ')) op.returnToPreviousChar();                    
                    }
                }
                get = false;
    		}
	        else if (state.equals(IState.NUM_DEC)){
	        	if(!isNumber(t)){
	        		if(t == '.'){
	        			currentToken = IStatusError.ERROR;
	        			state = IState.DONE;
	        		}
	        		if(t == 'e' || t == 'E'){
	        			if (previous != '.'){
		        			state = IState.NUM_EXP;
		        			previous = 'e';
	        			}
	        			else{
	        				currentToken = IStatusError.ERROR;
	        				state = IState.DONE;
	        			}
	        		}
	        		else if (isAlphaNum(t)) state = IState.NOT_NUM;
	        		else{
	        			currentToken = IToken.NUMBER;
	        			state = IState.DONE;
	        			if (! (t == ' ')) op.returnToPreviousChar();                       
	        		}
	        	}
	        	if (previous == '.') previous = EOF;
	        	get = false;
	        	
	        }
	        else if (state.equals(IState.NUM_EXP)){
	        	if(!isNumber(t)){
	        		if(previous == 'e' && t == '-') previous = EOF;
	        		else if(isAlphaNum(t)) state = IState.NOT_NUM;
	        		else{
	        			currentToken = IToken.NUMBER;
	        			state = IState.DONE;
	        			if (! (t == ' ')) op.returnToPreviousChar();                    
                        get = false;
	        		}
	        	}
	        	if (previous == '.') previous = EOF;
	        }
	        else if (state.equals(IState.NUM_HEXA)){
	        	if (!isNumber(t) && ((t != 'a') && (t != 'b') && (t != 'c') && (t != 'd') 
                        && (t != 'e') && (t != 'f'))){
	        		if(isAlphaNum(t)) state = IState.NOT_NUM;
	        		else{
	        			currentToken = IToken.NUMBER;
	        			state = IState.DONE;
	        			op.returnToPreviousChar();
	        			get = false;
	        		}
	        		
	        	}
	        }
            else if (state.equals(IState.IDENTIFIER)){
            	if (!isAlphaNum(t) || t == '_'){
                    currentToken = IToken.NAME;
                    state = IState.DONE;
                    get = false;
                    if (! (t == ' ')) op.returnToPreviousChar();
                }
            }
            else if (state.equals(IState.EQUAL)){
                if (t == '=')
                    currentToken = IToken.EQUAL;
                else {
                    currentToken = IToken.ASSIGNMENT;
                    if (! (t == ' ')) op.returnToPreviousChar();
                }
                state = IState.DONE;
                get = false;
            }
            else if (state.equals(IState.DIFFERENT)){
                if (t == '='){
                    state = IState.DONE;
                    currentToken = IToken.DIFFERENT;
                    get = false;
                } else {
                    currentToken = IStatusError.ERROR;
                    if (!(t == ' ')) op.returnToPreviousChar();
                    state = IState.DONE;
                }
            }
            else if (state.equals(IState.LESS_THEN)){
                if (t == '=')
                    currentToken = IToken.LESS_THEN_OR_EQUAL;
                else{
                    currentToken = IToken.LESS_THEN;
                    if (! (t == ' ')) op.returnToPreviousChar();
                }
                state = IState.DONE;
                get = false;
            }
            else if (state.equals(IState.GREATER_THEN)){
                if (t == '=')
                    currentToken = IToken.GREATER_THEN_OR_EQUAL;
                else{
                    currentToken = IToken.GREATER_THEN;
                    if (!(t == ' ')) op.returnToPreviousChar();
                }
                state = IState.DONE;
                get = false;
            }
            else if (state.equals(IState.DOT)){ //look at this
                if (t != '.') {
                    currentToken = IToken.DOT;
                    state = IState.DONE;
                    get = false;
                    if (!(t == ' ')) op. returnToPreviousChar();
                }else
                    state = IState.COLON;
            }
            else if (state.equals(IState.COLON)){
                if (t != '.') {
                    currentToken = IToken.CONCAT;
                    if (!(t == ' ')) op.returnToPreviousChar();
                } else
                    currentToken = IToken.LIST;
                state = IState.DONE;
                get = false;
            }
            else if (state.equals(IState.NOT_NUM)){
                if (t == '\t' || t == '\n' || t == ' ' || t =='\0'){
                    currentToken = IStatusError.ERROR;
                    state = IState.DONE;
                    get = false;
                }
            }
            else if(state.equals(IState.WORD)){
                while (t != previous && !op.isEOF() && t != '\n') t = op.getNextChar();
                if(op.isEOF() || t == '\n')
                    currentToken = IStatusError.ERROR;
                else
                    currentToken = IToken.STRING;
                state = IState.DONE;
                get = false;
            } 
	        else if(state.equals(IState.MULTI_WORD)){
                if(t != '='){
                    currentToken = "TK_LEFT_BRACKETS";
                    if (!(t == ' ')) op.returnToPreviousChar();
                } 
                else{
                    int nEquals = 0;
                    do{
                        nEquals++;
                        t = op.getNextChar();
                    } while(t != '[' && !op.isEOF());
                    
                    if(t == '['){
                        boolean error = false;
                        
                        do{
                            t = op.getNextChar();
                            if(t == '\\'){
                                t = op.getNextChar();
                                if(t == 't' || t == 'n' || t == '0' || t == 'r')
                                    error = true;
                            }
                        } while(t != ']' && !op.isEOF() && !error);
                        
                        if(t == ']'){
                            for(int i = 0; i < nEquals; i++){
                                t = op.getNextChar();
                                if(t != '=') break;
                            }

                            if(t == '='){
                                t = op.getNextChar();
                                if(t == ']') currentToken = IToken.STRING;
                                else currentToken = IStatusError.ERROR;
                            } 
                            else currentToken = IStatusError.ERROR;
                        } 
                        else  currentToken = IStatusError.ERROR;
                    } 
                    else currentToken = IStatusError.ERROR;
                }   
                state = IState.DONE;
                get = false;
            }
	        else if(state.equals(IState.EN_DASH)){
	        	if(t != '-'){
                    currentToken = IToken.SUBTRACT;
                    if (!(t == ' ')) op.returnToPreviousChar();
                    state = IState.DONE;
                } 
            	else state = IState.EN_DASH_COM;
                get = false;
            } 
            else if(state.equals(IState.EN_DASH_COM)){
            	if(t != '['){
                    while(!op.isEOF() && t != '\n') t = op.getNextChar();
                	currentToken = IToken.COMMENT;
                } 
                else if(t == '['){
                    int nEquals = 0;
                    t = op.getNextChar();
                    
                    while(!op.isEOF() && t == '='){
                    	t = op.getNextChar();
                        nEquals++;
                    }
                    if(t == '['){
                        while(t != ']' && !op.isEOF()) t = op.getNextChar();
                        if(t == ']'){
                            for(int i = 0; i < nEquals; i++){
                                t = op.getNextChar();
                                if(t != '=') break;
                            }

                            if(t == '='){
                                t = op.getNextChar();
                                if(t == ']') currentToken = IToken.COMMENT;
                                else currentToken = IStatusError.ERROR;
                            } 
                            else currentToken = IStatusError.ERROR;
                        } 
                        else currentToken = IStatusError.ERROR;
                    } 
                    else currentToken = IStatusError.ERROR;
                }
                state = IState.DONE;
                get = false;
            }
            if (get) token += t;
		    
		    if (currentToken == IToken.NAME)
		        currentToken = reservedWord(token);
	    }	
	    
	    return currentToken;
	}
	
	/**
	 * Verify if the identifier found is a reserved word
	 * 
	 * @param lexema the lexema
	 * @return the token
	 */
	private String reservedWord(String lexema){
	    
	    for (IReservedWord.lexema rw : EnumSet.range(IReservedWord.lexema.AND, IReservedWord.lexema.WHILE)){
//	    	System.out.println('\t'+rw.name());
	    	if(rw.name().equalsIgnoreCase(lexema))
	        	return rw.name();
	    }
	    
	    return IToken.NAME;
	}
	
	/**
	 * Verify is the character is a number
	 * @param c the character to be checked
	 * @return <b>true</b> if is a number
	 *         <p><b>false</b> otherwise</p>
	 */
	private  boolean isNumber(char c){
		return (c > 47) && (c < 58)? true : false;
	}
	
	/**
	 * Verify is the character is a letter
	 * @param c the character to be checked
	 * @return <b>true</b> if is a letter
	 *         <p><b>false</b> otherwise</p>
	 */
	private boolean isCharacter(char c){
		return ((c > 64) && (c < 91)) || ((c > 96) && (c < 123))? true : false;
	}
	
	/**
	 * Verify is the character is a number or a letter
	 * @param c the character to be checked
	 * @return <b>true</b> if is a number
	 *         <p><b>false</b> otherwise</p>
	 */
	private boolean isAlphaNum(char c){
		return isNumber(c) || isCharacter(c);
	}
	
}
